<template>
  <view style="margin:0;position:relative">
    <view :class="[loading ? 'loading' : '']">
      <div class="loadEffect">
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
        <span></span>
      </div>
    </view>
    <scroll-view scroll-x scroll-y class="th-table" @scroll="listenScroll">
      <table :width="realityWidth" class="sticky_top">
        <colgroup>
          <block v-for="(column, index) in columnsWithStyle" :key="index">
            <col v-if="column.type == 'selection'" :width="column.width || 40" />
            <col v-else-if="column.type == 'index'" :width="column.width || 40" />
            <col v-else-if="column.type == 'action'" :width="column.width || 80" />
            <col v-else :width="column.width || 80" />
          </block>
        </colgroup>
        <tr :class="[leftShadow ? 'left' : '', rightShadow ? 'right' : '']" v-for="(columns, index) in thColumn"
          :key="index">
          <th v-for="(column, index) in columns" :key="index" :class="column.classNames ? [...column.classNames] : []"
            :style="column.cssStyle" :colspan="column.colspan" :rowspan="column.rowspan">
            <view class="cell" :style="{ textAlign: column.align || 'center' }" @click="sortToggleHandle(column)">
              <block v-if="column.type && showHeaderCheckbox == 'selection'">
                <view class="checkbox" :class="[
                  allCheckedStatus === 1
                    ? 'checked half'
                    : allCheckedStatus === 2
                      ? 'checked all'
                      : '',
                  multiple ? '' : 'disabled',
                ]" @click="checkAllChange"></view>
              </block>
              <block v-else>
                {{ column.label }}
                <view v-if="column.sortable" class="sort" :class="[column.prop === sortKey ? 'sortable' : '', sortRule]">
                </view>
              </block>
            </view>
          </th>
        </tr>
      </table>
      <table :width="realityWidth">
        <colgroup>
          <block v-for="(column, index) in columnsWithStyle" :key="index">
            <col v-if="column.type == 'selection'" :width="column.width || 40" />
            <col v-else-if="column.type == 'index'" :width="column.width || 40" />
            <col v-else-if="column.type == 'action'" :width="column.width || 80" />
            <col v-else :width="column.width || 80" />
          </block>
        </colgroup>
        <tr v-for="(row, rowIndex) in dataList" :key="rowIndex"
          :class="[leftShadow ? 'left' : '', rightShadow ? 'right' : '', stripe ? 'stripe' : '']">
          <block v-for="(column, index) in columnsWithStyle" :key="index">
            <td :class="column.classNames ? [...column.classNames] : []" :style="column.cssStyle"
              v-if="column.customCell && column.customCell(row, rowIndex).rowSpan !== 0"
              :rowspan="column.customCell(row, rowIndex).rowSpan">
              <view class="cell" :style="{ textAlign: column.align || 'left' }">
                <block v-if="column.type == 'selection'">
                  <view class="checkbox" :class="[row.checked ? 'checked all' : '']" @click="checkChange(row)"></view>
                </block>
                <block v-else-if="column.type == 'index'">
                  <view class="table-index">
                    {{ rowIndex + 1 }}
                  </view>
                </block>
                <block v-else-if="column.type == 'action'">
                  <slot name="action" :scope="{ row, $index: rowIndex, column }"></slot>
                </block>
                <block v-else>
                  <block v-if="column.render">
                    <slot :name="column.render" :scope="{ row, $index: rowIndex, column }"></slot>
                  </block>
                  <block v-else>
                    {{ row[column.prop] }}
                  </block>
                </block>
              </view>
            </td>
          </block>
        </tr>
      </table>
      <view v-if="!data.length" class="empty">暂无数据</view>
    </scroll-view>
    <view style="margin: 15rpx 0">
      <slot name="pager"></slot>
    </view>
  </view>
</template>

<script>
export default {
  name: 'ThTable',
  props: {
    columns: {
      type: Array,
    },
    dataList: {
      type: Array,
    },
    stripe: {
      type: [Boolean, String],
      default: false,
    },
    showHeaderCheckbox: {
      type: Boolean,
      default: true,
    },
    multiple: {
      type: Boolean,
      default: false,
    },
    loading: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      leftShadow: false,
      rightShadow: false,
      tableWidth: 0,
      allCheckedStatus: 0,
      allSelectionRows: [],
      scrollLeft: 0,
      originalData: [],
      sortKey: '',
      sortRule: '',
    };
  },
  computed: {
    data() {
      return JSON.parse(JSON.stringify(this.dataList));
    },
    thColumn() {
      return this.flattenHandler(this.columns);
    },
    flattenColumn() {
      const columns = [];
      this.thColumn.forEach(list => {
        list.forEach(node => {
          if (node.children && node.children.length) {
            columns.concat(node.children)
          } else {
            columns.push(node)
          }
        })
      })
      return columns;
    },
    settleColumns() {
      const regularColumns = [];
      const withWidthColumns = [];
      const withoutWidthColumns = [];

      this.flattenColumn.forEach((col) => {
        if (col.type) {
          regularColumns.push(col);
        } else if (col.width) {
          withWidthColumns.push(col);
        } else {
          withoutWidthColumns.push(col);
        }
        if (!col.customCell) {
          col.customCell = () => {
            return {
              rowSpan: null,
            };
          };
        }
      });
      const regularWidth = regularColumns.length
        ? regularColumns
          .map((col) => {
            if (col.type === 'index' || col.type === 'selection') {
              return col.width || 40;
            }
            return col.width || 80;
          })
          .reduce((total, num) => {
            return total + num;
          })
        : 0;

      const withWidthColumnsWidth = withWidthColumns.length
        ? withWidthColumns
          .map((col) => {
            return col.width;
          })
          .reduce((total, num) => {
            return total + num;
          })
        : 0;

      const resetWidth = this.tableWidth - regularWidth - withWidthColumnsWidth;
      if (resetWidth > 0) {
        if (withoutWidthColumns.length) {
          const w = resetWidth / withoutWidthColumns.length;
          withoutWidthColumns.forEach((col) => {
            col.width = w >= 80 ? w : 80;
          });
        } else {
          withWidthColumns.forEach((col) => {
            col.width += (col.width * resetWidth) / withWidthColumnsWidth;
          });
        }
      }
      return this.flattenColumn;
    },
    columnWidth() {
      if (!this.settleColumns?.length) return 0;
      this.settleColumns
        .map((column) => {
          if (column.type && ['selection', 'index'].includes(column.type)) {
            return column.width || 40;
          }
          return column.width || 80;
        })
        .reduce((total, num) => {
          return total + num;
        });
    },
    realityWidth() {
      const { columnWidth, tableWidth } = this;
      return columnWidth > tableWidth ? columnWidth : tableWidth;
    },
    columnsWithStyle() {
      const fixedLeft = [];
      const noFixed = [];
      const fixedRight = [];
      this.settleColumns.forEach((col) => {
        if (col.fixed) {
          if (col.fixed === true || col.fixed === 'left') {
            fixedLeft.push(col);
          } else {
            fixedRight.push(col);
          }
        } else {
          noFixed.push(col);
        }
      });

      const fixedLeftLastIndex = fixedLeft.length - 1;
      const fixedRightFirstIndex = fixedLeft.length + noFixed.length;
      let left = 0;
      const right = 0;
      fixedLeft.forEach((col, index) => {
        if (index === fixedLeftLastIndex) {
          col.classNames = ['sticky_left', 'shadow_left'];
          col.cssStyle = {
            left: `${left}px`,
          };
        } else {
          col.classNames = ['sticky_left', 'after'];
          col.cssStyle = {
            left: `${left}px`,
          };
          left += col.width || ['index', 'selection'].includes(col.type) ? 60 : 80;
        }
      });

      function getRight(index) {
        const arr = fixedRight
          .filter((col, ind) => {
            return ind > index;
          })
          .map((col) => {
            return col.width || 80;
          });

        return arr.length
          ? arr.reduce((total, num) => {
            return total + num;
          })
          : 0;
      }
      fixedRight.forEach((col, index) => {
        col.classNames = ['sticky_right', index === 0 ? 'shadow_right' : 'before'];
        col.cssStyle = {
          right: `${getRight(index)}px`,
        };
      });
      return [...fixedLeft, ...noFixed, ...fixedRight];
    },
  },
  watch: {
    columnWidth: {
      handler() {
        if (this.tableWidth >= this.columnWidth) {
          this.rightShadow = false;
        } else if (this.columnWidth - this.tableWidth - this.scrollLeft === 0) {
          this.rightShadow = false;
        } else {
          this.rightShadow = true;
        }
      },
      immediate: true,
    },
    tableWidth() {
      if (this.tableWidth >= this.columnWidth) {
        this.rightShadow = false;
      } else if (this.columnWidth - this.tableWidth - this.scrollLeft === 0) {
        this.rightShadow = false;
      } else {
        this.rightShadow = true;
      }
    },
  },
  methods: {
    // 仅适用二维数组
    flattenHandler(list) {
      const flattenResult = [];
      function callBack(list, level = 0) {
        const next = level + 1;
        list.forEach(node => {
          if (flattenResult[level]) {
            flattenResult[level].push(node)
          } else {
            flattenResult[level] = [node]
          }
          if (node.children && node.children.length) {
            node.colspan = node.children.length;
            callBack(node.children, next)
          } else {
            node.colspan = 1;
          }
        })

      }

      callBack(list);

      flattenResult.forEach((list, index) => {
        list.forEach(node => {
          if (index === 0) {
            node.rowspan = node.children ? 1 : 2
          } else {
            node.rowspan = 1
          }

          node.colspan = node.colspan;
        })

      })
      return flattenResult;
    },
    listenScroll(e) {
      this.scrollLeft = e.detail.scrollLeft;
      if (e.detail.scrollLeft === 0) {
        this.leftShadow = false;
      } else {
        this.leftShadow = true;
      }

      if (this.columnWidth - this.tableWidth - e.detail.scrollLeft === 0) {
        this.rightShadow = false;
      } else {
        this.rightShadow = true;
      }
    },
    getWidth() {
      const el = uni.createSelectorQuery().select('.th-table');
      el.boundingClientRect((data) => {
        this.tableWidth = data.width - 2;
      }).exec();
    },
    checkAllChange() {
      if (!this.multiple) return;
      if (this.allCheckedStatus === 2) {
        this.allCheckedStatus = 0;
        this.dataList.forEach((row) => {
          row.checked = false;
        });
      } else {
        this.allCheckedStatus = 2;
        this.dataList.forEach((row) => {
          row.checked = true;
        });
      }

      this.allSelectionRows = this.data.filter((row) => {
        return row.checked;
      });
      this.$emit('allCheckChange', this.allSelectionRows)
    },
    checkChange(row) {
      if (!this.multiple) {
        this.data.forEach((row) => {
          row.checked = false;
        });
      }
      if (row.checked) {
        row.checked = false;
      } else {
        row.checked = true;
      }

      const fData = this.data.filter((row) => {
        return row.checked;
      });

      if (fData.length && this.multiple) {
        if (fData.length === this.data.length) {
          this.allCheckedStatus = 2;
        } else {
          this.allCheckedStatus = 1;
        }
      } else {
        this.allCheckedStatus = 0;
      }
      this.$forceUpdate();

      this.allSelectionRows = this.data.filter((row) => {
        return row.checked;
      });
      this.$emit('checkChange', { row, selection: this.allSelectionRows })
    },
    clearSelection() {
      this.data.forEach((row) => {
        row.checked = false;
      });
      this.allCheckedStatus = 0;
      this.$forceUpdate();
    },
    getSelectionRows() { },
    toggleRowSelection() { },
    toggleAllSelection() { },
    sortFun(arr, name, type) {
      const compare = (prop) => {
        return (obj1, obj2) => {
          const val1 = obj1[prop];
          const val2 = obj2[prop];
          if (val1 < val2) {
            return -1;
          }
          if (val1 > val2) {
            return 1;
          }
          return 0;
        };
      };
      if (type === 'asc') {
        return arr.sort(compare(name));
      }
      if (type === 'desc') {
        return arr.sort(compare(name)).reverse();
      }
      return this.originalData;
    },
    sortToggleHandle(column) {
      if (!column.sortable) return;
      if (this.sortKey !== column.prop) {
        this.sortRule = '';
      }

      this.sortKey = column.prop;
      if (this.sortRule) {
        if (this.sortRule === 'asc') {
          this.sortRule = 'desc';
        } else {
          this.sortRule = '';
        }
      } else {
        this.sortRule = 'asc';
      }

      this.data = this.sortFun(this.data, this.sortKey, this.sortRule);
    },
  },
  mounted() {
    this.originalData = [...this.dataList];
    this.$nextTick(() => {
      this.getWidth();
    });
    uni.onWindowResize(() => {
      this.getWidth();
    });
  },
};
</script>

<style lang="scss" scoped>
$border-width: 1px;
$border-color: #d7d7d7;

.th-table {
  border: $border-width solid $border-color;
  position: relative;
  box-sizing: border-box;
  max-height: 500px;
}

table {
  border-collapse: collapse;
  table-layout: fixed;

  tr {
    min-height: 68rpx;

    &:nth-child(2) {
      td {
        border-top: 0;
      }
    }

    &:nth-last-child(2) {

      /* 样式规则 */
      td[rowspan] {
        border-bottom: 0;
      }

      td[rowspan='1'] {
        border-bottom: 1px solid $border-color;
      }
    }

    td,
    th {
      &:last-child {
        border-right: 0;
      }
    }

    &.stripe:nth-child(odd) {
      td {
        background: #f4f9ff;
      }
    }

    // &:last-child {
    //   .sticky_left {
    //     &::before {
    //       content: '';
    //       height: 0;
    //     }
    //   }
    // }
  }

  th {
    box-sizing: border-box;
    height: 88rpx;
    padding: 10rpx 0;
    font-size: 28rpx;
    color: #333;
    background: rgb(221, 235, 254);
    border-right: $border-width solid $border-color;
    border-bottom: $border-width solid $border-color;

    .cell {
      display: flex;
      justify-content: center;
      align-items: center;

      .sort {
        display: flex;
        flex-direction: column;
        justify-content: center;
        font-size: 28px;
        margin-left: 10rpx;

        .shang {
          margin-bottom: 5rpx;
        }

        &.sortable {
          &.asc {
            .shang {
              color: #409eff;
            }
          }

          &.desc {
            .xia {
              color: #409eff;
            }
          }
        }
      }
    }
  }

  td {
    box-sizing: border-box;
    border-right: $border-width solid $border-color;
    border-top: $border-width solid $border-color;
    font-size: 28rpx;
    padding: 8rpx 0;
    min-height: 68rpx;
    height: $border-width;
    color: #606266;
    background: #fff;

    &.sticky_left,
    &.sticky_right {
      background: #fff;
    }

    // &[rowspan]{
    // 	color:red;
    // }
  }

  .cell {
    box-sizing: border-box;
    // overflow: hidden;
    text-overflow: ellipsis;
    white-space: normal;
    word-break: break-all;
    padding-left: 10rpx;
    padding-right: 10rpx;
  }

  &.sticky_top {
    position: sticky;
    top: 0;
    z-index: 9;
  }

  .sticky_left {
    position: sticky;
    // top: auto;
    z-index: 8;

    &.after::after {
      content: '';
      width: $border-width;
      height: 100%;
      background: #dedede;
      position: absolute;
      top: 0;
      right: -1px;
      z-index: 9;
    }

    &::before {
      content: '';
      width: 100%;
      height: $border-width;
      background: #dedede;
      position: absolute;
      top: -1px;
      left: 0;
    }
  }

  .sticky_right {
    position: sticky;
    top: auto;
    z-index: 8;

    &.before::after {
      content: '';
      width: 1px;
      height: 100%;
      background: #dedede;
      position: absolute;
      top: 0;
      left: -1px;
      z-index: 9;
    }
  }

  .left {
    .shadow_left::after {
      content: '';
      width: 10rpx;
      height: 100%;
      background-image: linear-gradient(to left, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1));
      position: absolute;
      top: 0;
      right: -10rpx;
      z-index: 9;
      transition: all 0.2s;
    }
  }

  .right {
    .shadow_right::after {
      content: '';
      width: 10rpx;
      height: 100%;
      background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.1));
      position: absolute;
      top: 0;
      left: -10rpx;
      z-index: 9;
      transition: all 0.2s;
    }
  }
}

.table-index {
  min-height: 68rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.checkbox {
  width: 32rpx;
  height: 32rpx;
  border-radius: 6rpx;
  outline: 0;
  border: $border-width solid $border-color;
  background-color: #fff;
  position: relative;
  display: inline-flex;
  -webkit-align-items: center;
  -webkit-box-align: center;
  align-items: center;
  vertical-align: middle;

  &.checked {
    background: #409eff;
    border: $border-width solid #409eff;
  }

  &.half::after {
    content: '';
    position: absolute;
    display: block;
    background-color: #fff;
    height: 6rpx;
    transform: scale(0.6);
    left: 0;
    right: 0;
  }

  &.all::after {
    content: '¬';
    color: #fff;
    position: absolute;
    font-size: 30rpx;
    top: 50%;
    left: 50%;
    font-weight: bold;
    transform: translate(-40%, -50%) rotate(130deg);
  }

  &.disabled {
    background: #e6e6e6;
  }
}

.empty {
  padding: 15rpx 0;
  color: #cdcdcd;
  text-indent: calc(50% - 2em);
}

.loading {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 99999;
  background: rgba(255, 255, 255, 0.4);

  .loadEffect {
    width: 100px;
    height: 100px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: scale(0.3) translate(-100%, -100%);
  }

  .loadEffect span {
    display: inline-block;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #409eff;
    position: absolute;
    -webkit-animation: load 1.04s ease infinite;
  }

  @keyframes load {
    0% {
      opacity: 1;
    }

    100% {
      opacity: 0.2;
    }
  }

  .loadEffect span:nth-child(1) {
    left: 0;
    top: 50%;
    margin-top: -8px;
    -webkit-animation-delay: 0.13s;
  }

  .loadEffect span:nth-child(2) {
    left: 14px;
    top: 14px;
    -webkit-animation-delay: 0.26s;
  }

  .loadEffect span:nth-child(3) {
    left: 50%;
    top: 0;
    margin-left: -8px;
    -webkit-animation-delay: 0.39s;
  }

  .loadEffect span:nth-child(4) {
    top: 14px;
    right: 14px;
    -webkit-animation-delay: 0.52s;
  }

  .loadEffect span:nth-child(5) {
    right: 0;
    top: 50%;
    margin-top: -8px;
    -webkit-animation-delay: 0.65s;
  }

  .loadEffect span:nth-child(6) {
    right: 14px;
    bottom: 14px;
    -webkit-animation-delay: 0.78s;
  }

  .loadEffect span:nth-child(7) {
    bottom: 0;
    left: 50%;
    margin-left: -8px;
    -webkit-animation-delay: 0.91s;
  }

  .loadEffect span:nth-child(8) {
    bottom: 14px;
    left: 14px;
    -webkit-animation-delay: 1.04s;
  }
}
</style>
